#include "DirectShowMediaFile.h"


#define REGISTER_FILTERGRAPH


using namespace Scenic::Renderer;


HRESULT AddToROT(IUnknown *pUnkGraph);
void RemoveFromROT(void);


DirectShowMediaFile::DirectShowMediaFile(const char *filename, Image *image)
{
	printf("DirectShowMediaFile::DirectShowMediaFile(\"%s\")\n", filename);
	this->filename = strdup(filename);
	this->image = image;
	imageRenderer = 0;
	initRenderer();
}

DirectShowMediaFile::~DirectShowMediaFile()
{
	release();
}

bool DirectShowMediaFile::initRenderer()
{
    HRESULT hr = S_OK;
    CComPtr<IPin> pFSrcPinOut;
    
    CoInitialize (NULL);
	
    // Create the filter graph
    if (FAILED(graphBuilder.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC)))
	{
		printf("Failed to create the filter graph\n");
        return false;
	}
	
#ifdef REGISTER_FILTERGRAPH
    // Register the graph in the Running Object Table (for debug purposes)
    AddToROT(graphBuilder);
#endif
    
    // Create the Texture Renderer object
    imageRenderer = new ImageRenderer(image);
    
    // Get a pointer to the IBaseFilter on the TextureRenderer, add it to graph
    renderer = imageRenderer;
    if (FAILED(hr = graphBuilder->AddFilter(renderer, L"IMAGERENDERER")))
    {
        printf("Could not add renderer filter to graph!  hr=0x%x\n", hr);
        return false;
    }
    
	USES_CONVERSION;
	
	if(strcmp(filename, "capture://") == 0)
	{
		hr = captureGraphBuilder.CoCreateInstance(CLSID_CaptureGraphBuilder2 , NULL, CLSCTX_INPROC);
        if (FAILED(hr))
        {
            printf("Could not create the capture graph builder!  hr=0x%x\n", hr);
            return false;
        }
    
        // Attach the existing filter graph to the capture graph
        hr = captureGraphBuilder->SetFiltergraph(graphBuilder);
        if (FAILED(hr))
        {
            printf("Failed to set capture filter graph!  hr=0x%x\n", hr);
            return false;
        }

        sourceFilter = findCaptureDevice();
        if(!sourceFilter)
        {
			return false;
        }
   
        // Add the returned capture filter to our graph.
        hr = graphBuilder->AddFilter(sourceFilter, L"Video Capture");
        if (FAILED(hr))
        {
            return false;
        }

        hr = captureGraphBuilder->RenderStream (&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
                                       sourceFilter, NULL, renderer);
        if (FAILED(hr))
        {
			printf("Could not render stream\n");
            return false;
        }

		hr = captureGraphBuilder->FindInterface(NULL /* &PIN_CATEGORY_PREVIEW */, 0, sourceFilter,
			IID_IAMStreamConfig, (void**)&streamConfig);

		if(FAILED(hr))
		{
			printf("Failed to get IAMStreamConfig interface!  hr=0x%x\n", hr);
		}
	}
	else
	{
	    WCHAR *wFileName = A2W(filename);
	
	    // Add the source filter to the graph.
		hr = graphBuilder->AddSourceFilter (wFileName, L"SOURCE", &sourceFilter);
    
	   // If the media file was not found, inform the user.
	    if (hr == VFW_E_NOT_FOUND)
	    {
	        printf("Could not add source filter to graph!  (hr==VFW_E_NOT_FOUND)\n");
	        return false;
	    }
		if(FAILED(hr))
		{
			printf("Could not add source filter to graph!  hr=0x%x", hr);
			return false;
		}
 		if(FAILED(hr = sourceFilter->FindPin(L"Output", &pFSrcPinOut)))
		{
			printf("Could not find output pin!  hr=0x%x", hr);
			return false;
		}
		
#ifdef NO_AUDIO_RENDERER
		
		// If no audio component is desired, directly connect the two video pins
		// instead of allowing the Filter Graph Manager to render all pins.
		
		CComPtr<IPin> pFTRPinIn;      // Texture Renderer Input Pin
		
		// Find the source's output pin and the renderer's input pin
		if (FAILED(hr = pFTR->FindPin(L"In", &pFTRPinIn)))
		{
			printf("Could not find input pin!  hr=0x%x", hr);
			return false;
		}
		
		// Connect these two filters
		if (FAILED(hr = graphBuilder->Connect(pFSrcPinOut, pFTRPinIn)))
		{
			printf("Could not connect pins!  hr=0x%x", hr);
			return false;
		}
		
#else
		
		// Render the source filter's output pin.  The Filter Graph Manager
		// will connect the video stream to the loaded CTextureRenderer
		// and will load and connect an audio renderer (if needed).
		
		if (FAILED(hr = graphBuilder->Render(pFSrcPinOut)))
		{
			printf("Could not render source output pin!  hr=0x%x", hr);
			return false;
		}
#endif
	}
	
	// Get the graph's media control, event & position interfaces
    graphBuilder.QueryInterface(&mediaControl);
    graphBuilder.QueryInterface(&mediaPosition);
    graphBuilder.QueryInterface(&mediaEvent);
    
	printf("Video renderer intialized.\n");
    return true;
}

void DirectShowMediaFile::release()
{
	printf("SimpleMediaFile::release\n");
#ifdef REGISTER_FILTERGRAPH
    // Pull graph from Running Object Table (Debug)
    RemoveFromROT();
#endif
	
    // Shut down the graph
    if (!(!mediaControl)) mediaControl->Stop();
	
    if (!(!mediaControl)) mediaControl.Release();
    if (!(!mediaEvent)) mediaEvent.Release();
    if (!(!mediaPosition)) mediaPosition.Release();
    if (!(!graphBuilder)) graphBuilder.Release();
    if (!(!renderer)) renderer.Release();

	releaseReference();
}

IBaseFilter *DirectShowMediaFile::findCaptureDevice()
{
    HRESULT hr = S_OK;
    CComPtr <IMoniker> pMoniker;
    CComPtr <ICreateDevEnum> pDevEnum;
    CComPtr <IEnumMoniker> pClassEnum;

    IBaseFilter * pSrc = NULL;
    ULONG cFetched;

    try
    {
        // Create the system device enumerator
        hr = CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,
                            IID_ICreateDevEnum, (void **) &pDevEnum);
        if (FAILED(hr))
        {
            printf("Couldn't create system device enumerator.  hr=0x%x\n", hr);
            return NULL;
        }

        // Create an enumerator for the video capture devices
        hr = pDevEnum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
        if (FAILED(hr))
        {
            printf("Couldn't create class enumerator for video input device category.  hr=0x%x\n", hr);
            return NULL;
        }

        // If there are no enumerators for the requested type, then 
        // CreateClassEnumerator will succeed, but pClassEnum will be NULL.
        if (pClassEnum == NULL)
        {
            printf("No video capture device was detected.\n");
            return NULL;
        }

        // Use the first video capture device on the device list.
        // Note that if the Next() call succeeds but there are no monikers,
        // it will return S_FALSE (which is not a failure).  Therefore, we
        // check that the return code is S_OK instead of using SUCCEEDED() macro.
        if (S_OK == (pClassEnum->Next (1, &pMoniker, &cFetched)))
        {
            // Bind Moniker to a filter object
            hr = pMoniker->BindToObject(0,0,IID_IBaseFilter, (void**)&pSrc);
            if (FAILED(hr))
            {
                printf("Couldn't bind moniker to filter object.  hr=0x%x\n");
                return NULL;
            }
        }
        else
        {
            printf("Unable to access video capture device.\n");   
            return NULL;
        }

        // Copy the found filter pointer to the output parameter.
        // Do NOT Release() the reference, since it will still be used
        // by the calling function.
        return pSrc;

    }// try
    catch(...)
    {
        printf("Application encountered an unexpected error when trying to find capture device.\n");
    }
    return NULL;
}

void DirectShowMediaFile::play()
{
	mediaControl->Run();
}

void DirectShowMediaFile::pause()
{
	mediaControl->Pause();
}

double DirectShowMediaFile::getCurrentPosition()
{
	REFTIME time;

	mediaPosition->get_CurrentPosition(&time);
	return time;
}

void DirectShowMediaFile::setCurrentPosition(double position)
{
	mediaPosition->put_CurrentPosition((REFTIME)position);
}

double DirectShowMediaFile::getLength()
{
	REFTIME time;

	mediaPosition->get_Duration(&time);
	return time;
}

void DirectShowMediaFile::enumerateOutputFormats()
{
	HRESULT hr;
	int width;
	int height;
	int count = 0, size = 0;

	if(streamConfig == NULL)
		return;

	jobject obj = getJavaObject();

	if(!obj)
		return;

	JNIEnvPtr env(getJavaVM());
	jclass clazz = env->GetObjectClass(obj);
	jmethodID addOutputFormatMethod = env->GetMethodID(clazz, "addOutputFormat", "(III)V");
	
	hr = streamConfig->GetNumberOfCapabilities(&count, &size);

	// Check the size to make sure we pass in the correct structure.
	if(size != sizeof(VIDEO_STREAM_CONFIG_CAPS))
		return;

	// Use the video capabilities structure.
	
	for(int i = 0; i < count; i++)
	{
		VIDEO_STREAM_CONFIG_CAPS scc;
		AM_MEDIA_TYPE *mediaType;
		
		hr = streamConfig->GetStreamCaps(i, &mediaType, (BYTE*)&scc);
		if(SUCCEEDED(hr))
		{
			if(!IsEqualGUID( mediaType->majortype,    MEDIATYPE_Video)  ||
				!IsEqualGUID( mediaType->subtype, MEDIASUBTYPE_RGB24))
			{
				printf("Media type not ok\n");
				continue;
			}
			
			if(mediaType->formattype == FORMAT_VideoInfo)
			{
				VIDEOINFOHEADER *info = (VIDEOINFOHEADER*)mediaType->pbFormat;
				
				width  = info->bmiHeader.biWidth;
				height = info->bmiHeader.biHeight;
			}
			else
			{
				VIDEOINFOHEADER2 *info = (VIDEOINFOHEADER2*)mediaType->pbFormat;
				
				width  = info->bmiHeader.biWidth;
				height = info->bmiHeader.biHeight;
			}
			env->CallVoidMethod(obj, addOutputFormatMethod, width, height, FMT_R8G8B8);		
					
			DeleteMediaType(mediaType);
		}
	}
}

void DirectShowMediaFile::setOutputFormat(int width, int height, Format pixelFormat)
{
	HRESULT hr;
	int count = 0, size = 0;
	
	if(streamConfig == NULL)
		return;

	hr = streamConfig->GetNumberOfCapabilities(&count, &size);

	// Check the size to make sure we pass in the correct structure.
	if(size != sizeof(VIDEO_STREAM_CONFIG_CAPS))
		return;

	// Use the video capabilities structure.
	
	imageRenderer->setDesiredFormat(width, height, pixelFormat);

	for(int i = 0; i < count; i++)
	{
		VIDEO_STREAM_CONFIG_CAPS scc;
		AM_MEDIA_TYPE *format;
		
		hr = streamConfig->GetStreamCaps(i, &format, (BYTE*)&scc);
		if(SUCCEEDED(hr))
		{
			int w, h;

			if(!IsEqualGUID( format->majortype, MEDIATYPE_Video)  ||
				!IsEqualGUID( format->subtype, MEDIASUBTYPE_RGB24))
			{
				printf("Media type not ok\n");
				continue;
			}
			
			if(format->formattype == FORMAT_VideoInfo)
			{
				VIDEOINFOHEADER *info = (VIDEOINFOHEADER*)format->pbFormat;
				
				w  = info->bmiHeader.biWidth;
				h = info->bmiHeader.biHeight;
				info->AvgTimePerFrame = 10000000 / 18;
			}
			else
			{
				VIDEOINFOHEADER2 *info = (VIDEOINFOHEADER2*)format->pbFormat;
				
				w  = info->bmiHeader.biWidth;
				h = info->bmiHeader.biHeight;
				info->AvgTimePerFrame = 10000000 / 18;
			}
			if(w == width && h == height)
			{
				printf("Setting new format\n");
				mediaControl->Stop();
				hr = streamConfig->SetFormat(format);
				if(FAILED(hr)) {
					printf("DirectShowMediaFile::setOutputFormat - could not set format\n");
					return;
				}
				return;
			}
					
			DeleteMediaType(format);
		}
	}
}




#ifdef REGISTER_FILTERGRAPH

//-----------------------------------------------------------------------------
// Running Object Table functions: Used to debug. By registering the graph
// in the running object table, GraphEdit is able to connect to the running
// graph. This code should be removed before the application is shipped in
// order to avoid third parties from spying on your graph.
//-----------------------------------------------------------------------------
DWORD dwROTReg = 0xfedcba98;

HRESULT AddToROT(IUnknown *pUnkGraph) 
{
    IMoniker * pmk;
    IRunningObjectTable *pROT;
    if (FAILED(GetRunningObjectTable(0, &pROT))) {
        return E_FAIL;
    }
	
    WCHAR wsz[256];
    wsprintfW(wsz, L"FilterGraph %08x  pid %08x\0", (DWORD_PTR) 0, GetCurrentProcessId());
	
    HRESULT hr = CreateItemMoniker(L"!", wsz, &pmk);
    if (SUCCEEDED(hr)) 
    {
        // Use the ROTFLAGS_REGISTRATIONKEEPSALIVE to ensure a strong reference
        // to the object.  Using this flag will cause the object to remain
        // registered until it is explicitly revoked with the Revoke() method.
        //
        // Not using this flag means that if GraphEdit remotely connects
        // to this graph and then GraphEdit exits, this object registration 
        // will be deleted, causing future attempts by GraphEdit to fail until
        // this application is restarted or until the graph is registered again.
        hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph, 
			pmk, &dwROTReg);
        pmk->Release();
    }
	
    pROT->Release();
    return hr;
}

void RemoveFromROT(void)
{
    IRunningObjectTable *pirot=0;
	
    if (SUCCEEDED(GetRunningObjectTable(0, &pirot))) 
    {
        pirot->Revoke(dwROTReg);
        pirot->Release();
    }
}

#endif
